import * as method from "@mac-xiang/method";
import * as crypto from "crypto";
import https from "https";
import axios from "axios";
import * as xml2js from "xml2js";

import { Callback, fun, oa } from "../itf";
import { ORDER, REQUEST } from "../struct";

import * as itf from "./itf";



export class PAY {
  type = ["NATIVE", "JSAPI", "APP", "NWEB"];
  config: itf.Config;
  constructor (param: itf.Create) {
    this.config = {
      partner_key: param.partnerKey,
      notify_url: param.notifyUrl ? param.notifyUrl : "",
      public: { appid: param.appId, mch_id: param.mchId },
      private: param.private
    };
  }
  private sign(p: oa, ps?: any) {
    if (!method.isNst(p.nonce_str)) p.nonce_str = method.randStr();
    const excludeSign = ["pfx", "partner_key", "sign", "key"];
    let t = Object.keys(p).filter((k) => {
      return method.isNst(p[k]) && p[k].toString().replace(/ /g, "").length && excludeSign.indexOf(k) < 0;
    }).sort().map((k) => {
      return `${k}=${p[k]}`;
    }).join("&") + `&key=${this.config.partner_key}`;
    if (ps) console.log(t);
    p.sign = method.hash(t).toUpperCase();
    return p;
  }
  private buildXML(p: any) {
    return new xml2js.Builder().buildObject(p);
  }
  private parseXML(p: any) {
    return new xml2js.
      Parser({ trim: true, explicitArray: false, explicitRoot: false }).
      parseStringPromise(p);
  }
  private validate(param: oa) {
    let ret: oa = { err: "校验错误", raw: param };
    const sign = param.sign;
    if (typeof sign == "string") {
      this.sign(param);
      if (param.sign == sign) ret = param;
    }
    return ret;
  }
  private ret(p: any, fun: fun) {
    const r = new REQUEST(p);
    fun(r);
    return r;
  }
  create(p: ORDER) {
    return new Promise<REQUEST>((resolve) => {
      const q: any = Object.assign({
        body: p.title || "支付定金",
        out_trade_no: p.order,
        total_fee: p.money,
        spbill_create_ip: p.ip,
        trade_type: p.trade_type || this.type[0],
        notify_url: this.config.notify_url,
      }, this.config.public);
      if (q.trade_type == "JSAPI") {
        if (typeof p.openid != "string") return resolve(new REQUEST({ err: "JSAPI支付时,需要clientCode", p }));
        q.openid = p.openid;
      }
      if (typeof p.appid == "string") q.appid = p.appid;
      axios({
        method: "post",
        url: "https://api.mch.weixin.qq.com/pay/unifiedorder",
        data: this.buildXML(this.sign(q)),
      }).then(async res => {
        let r = await this.parseXML(res.data);
        resolve(new REQUEST(Object.assign({
          money: p.money,
          order: q.out_trade_no
        }, r)));
      }).catch(e => {
        resolve(new REQUEST({ err: e }));
      });
    });
  }

  query(order: string): Promise<REQUEST> {
    return new Promise((resolve) => {
      const q = { xml: this.sign(Object.assign({ out_trade_no: order }, this.config.public)) };
      axios({
        url: "https://api.mch.weixin.qq.com/pay/orderquery",
        method: "POST", data: this.buildXML(q)
      }).then(async r => {
        this.parseXML(r.data).then(d => {
          this.ret(Object.assign({ order: order }, d), resolve);
        }).catch(e => {
          this.ret({ err: "解析错误", raw: r.data }, resolve);
        });
      }).catch(e => {
        this.ret({ err: "网络错误", raw: e }, resolve);
      });
    });

  }
  close(order: string): Promise<REQUEST> {
    return new Promise(resolve => {
      const q = { xml: this.sign(Object.assign({ out_trade_no: order }, this.config.public)) };
      axios({
        method: "post",
        url: "https://api.mch.weixin.qq.com/pay/orderquery",
        data: this.buildXML(q)
      }).then(async r => {
        const d = await this.parseXML(r.data);
        this.ret(Object.assign({ order: order }, d), resolve);
      }).catch(e => {
        this.ret({ err: "网络错误", raw: e }, resolve);
      });
    });
  }
  back(param: any): Promise<REQUEST> {
    return new Promise(async resolve => {
      const p = new ORDER(param);
      if (!p.order) return this.ret({ err: "缺少参数", raw: p }, resolve);
      if (typeof p.orderT != "string") p.orderT = method.randStr("wx");
      if (!p.money || !p.orderM) {
        const r = await this.query(p.order);
        if (r.check) {
          if (p.orderM != r.payment) p.orderM = parseInt(r.payment.toString());
          p.money = (p.money < p.orderM && p.money > 0) ? parseInt(p.money.toString()) : p.orderM;
        } else return this.ret(r, resolve);
      }
      const q = Object.assign({
        out_trade_no: p.order,
        out_refund_no: p.orderT,
        total_fee: p.orderM,
        refund_fee: p.money,
        refund_desc: p.title,
        notify_url: p.backUrl ? p.backUrl : this.config.notify_url
      }, this.config.public);
      const httpsAgent = new https.Agent({
        cert: "-----BEGIN CERTIFICATE-----\nMIID8DCCAtigAwIBAgIUZeCdoqu0fABi/VNalhZAJ1R28e8wDQYJKoZIhvcNAQEL\nBQAwXjELMAkGA1UEBhMCQ04xEzARBgNVBAoTClRlbnBheS5jb20xHTAbBgNVBAsT\nFFRlbnBheS5jb20gQ0EgQ2VudGVyMRswGQYDVQQDExJUZW5wYXkuY29tIFJvb3Qg\nQ0EwHhcNMjAxMjA0MDkxNDI5WhcNMjUxMjAzMDkxNDI5WjCBgTETMBEGA1UEAwwK\nMTYwNDExOTIyNTEbMBkGA1UECgwS5b6u5L+h5ZWG5oi357O757ufMS0wKwYDVQQL\nDCTpnZLmtbflpKnmgqbnp5HmioDliJvmlrDmnInpmZDlhazlj7gxCzAJBgNVBAYM\nAkNOMREwDwYDVQQHDAhTaGVuWmhlbjCCASIwDQYJKoZIhvcNAQEBBQADggEPADCC\nAQoCggEBAMDXC7gDjy7VecWBbmDjPC3M3/9MhvEnofHzYaPMY0PpV9PQDyR+rtQu\nhOy8p43smBmfG4p0G7wwoadCe18q2695zgtm+3zJnUTkKxUiWpU4FGDW/L0+9rVu\nzlkMC0fr568NfkbCiTvy57kGTsfRhUb+9VSmtpB0LszGbtqJp93UXZQ6NonHKV5i\ngGeKLYeEEs+7MCwCVIGG8t/hBWOUJVKQN5fbjeD8frNtryUcXGIlm9PE+gDWEV7o\nU/Vwik7x63Eowvuc6OX4YnV4qRNOkLXivPLY6GmisuK/hU0dEgQVaRZPVDFuJ35O\nkyhGAddnMZHxP3wosgaPA6YHB6hJ/PMCAwEAAaOBgTB/MAkGA1UdEwQCMAAwCwYD\nVR0PBAQDAgTwMGUGA1UdHwReMFwwWqBYoFaGVGh0dHA6Ly9ldmNhLml0cnVzLmNv\nbS5jbi9wdWJsaWMvaXRydXNjcmw/Q0E9MUJENDIyMEU1MERCQzA0QjA2QUQzOTc1\nNDk4NDZDMDFDM0U4RUJEMjANBgkqhkiG9w0BAQsFAAOCAQEAMaDH7U+jAks6EV+Y\nXIQ5U7HfTTKL/OPrMrXipa61G788czOho6Kn67C6YD8boCGe8g9fzHbdeMkc7Ueq\nzronETYZAV80Q6jlIe6owkQf7CWb/eafo2lyW18al8QxP+zPXI3tvWPrzS1a9bcK\ngDf5xNheRFErzHfBT3MQfAz1WSF350bdz5CfDHQBAaQB1xmTFwy6YSiaPgPa29mY\ncVIocv8TwssiF+x7zdGe3lhKiWZ/E1eQ+fU1oJtMDKXxH3lL5vFHl5MuK3fMSna4\nSvJ+Fab/In/1sS5woQquxgYioFkexgPs1TF3w0A/h9noLEY0E3oip+K5nCSTXEXk\nbGgUSg==\n-----END CERTIFICATE-----\n",
        key: "-----BEGIN PRIVATE KEY-----\nMIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQDA1wu4A48u1XnF\ngW5g4zwtzN//TIbxJ6Hx82GjzGND6VfT0A8kfq7ULoTsvKeN7JgZnxuKdBu8MKGn\nQntfKtuvec4LZvt8yZ1E5CsVIlqVOBRg1vy9Pva1bs5ZDAtH6+evDX5Gwok78ue5\nBk7H0YVG/vVUpraQdC7Mxm7aiafd1F2UOjaJxyleYoBnii2HhBLPuzAsAlSBhvLf\n4QVjlCVSkDeX243g/H6zba8lHFxiJZvTxPoA1hFe6FP1cIpO8etxKML7nOjl+GJ1\neKkTTpC14rzy2OhporLiv4VNHRIEFWkWT1Qxbid+TpMoRgHXZzGR8T98KLIGjwOm\nBweoSfzzAgMBAAECggEATy28/RBRERzMBAFx7cw/oFqXun6UhZ4NS4fwFk5Mj8zW\np/ysywJNDIgRQhbwAUEc7qA6sa2c2bdqGIAeoeO6EiHELUBakFFEBs5cti/F27EM\nBPshCR86MyLNDRj55M0C8whqSemEaZ4B8SGa5UatH4p99wUAvu1FbgUn7QlKaLUc\nMlV7uG3Gp05Fhj39UEHaiIev+DPHaVZWFlYmFm2QQIk1MP5L0YM7u6nG1xI9v3OX\npDHGWVXjLK1vZ2kF/6Bn3ojfSA9Om/ZwFDQvxPavnwoHagSDvTwfpRrOj6+RaupA\nindQED8Mrdb3som1mnqruhMSPF/+7s0BKPygOntKQQKBgQDkrOpLhb+XG5YMzBI7\nGUI841Lp7yCa0f4b2Byax7m52FCwvoX3jKKYY1igXGGAmkx1EmNPaNKJHBx96tme\n+3Clf1IKeCaOZDjvHCh0C8Ees637BYMimdu82EIXssvWWBhF8GRd2rScHvH0cmJA\nFjC3Z92g4iNSdCv+n9TmDjLTEwKBgQDX4fCoi6GEzGmsj1zH0TJK4RzWMqXGTx39\nQkX3xlqA9Uq+7e0v9y89E8yxZB0neS1wxdbjGmxZAHPVxFZijaNugGH2d2S/QyEW\nc9gUcbW4WrkzZ3rqeD6OmRf9BjptmpUJJniTa85EAMDBIp257sySCUkN3ioQa8p6\n1Jz+r6mKoQKBgDzxgHekK41gArtiX7F0Z9zGUMCbxDq+oFRXYjg15l+LMOYJwLZa\nurKWN3Z1aF7XwGyq++Cb9RApd8ZvIpRmOn1A02KK5uun9ixzeqyCvXMrO8DclThR\nfOh1UhfyxLRZQg7RpE4HDX9WF/Gn0lt2DoGyorRP0E4XO+peOmUdf5oVAoGAThlJ\n89+XzjAZJrkyioMu25H22bK28DgdFEsOfuW+Rhr3MH1oqkqVR1ZLXf1m4qne+4ZH\nCB++BLC6NyJxUfQFpOtDBWQKCXtik/nSuQquAASqLWOJPxvDHdlaqUnAh3g95HKL\nemGs3EMdeIFqaCJw1YAQcrjlpM0qEieqqCSqhCECgYEAjYXuYam6aRZn8q8/Fc4L\ntCbZXbij3/pC3UiK1+4LL9W0eZ0qTeYX8QeF3w6K7Aa+fj2IrNlCspeCaberFgmT\nur7Wz2bPDyGFZ1oBZuZKYsTbOV24gwifcSD9zbJ5hJxTlB8JlGXKzNsJTgUk0jMb\nVa2gQFClCM7ZMkRUkr6QdQs=\n-----END PRIVATE KEY-----\n",
      });
      // console.log(httpsAgent);
      axios({
        url: "https://api.mch.weixin.qq.com/secapi/pay/refund",
        method: "post",
        data: this.buildXML({ xml: this.sign(q) }),
        httpsAgent
      }).then(async r => {
        const d = this.validate(await this.parseXML(r.data));
        // console.log(this.buildXML({ xml: q }));
        // console.log(q);
        this.ret(Object.assign({ order: p.order, money: p.money }, d), resolve);
      }).catch(e => {
        this.ret({ err: "网络错误", raw: e }, resolve);
      });
    });
  }
  async notify(req: oa): Promise<REQUEST> {
    let res: oa = req;
    if (res.return_code == "SUCCESS") { // AES-256-ECB
      if (res.req_info) { // 退款
        try {
          const key = method.hash(this.config.partner_key);
          const iv = Buffer.alloc(0);
          const decipher = crypto.createDecipheriv('aes-256-ecb', key, iv);
          let xml = decipher.setAutoPadding(false).update(res.req_info, "base64", "utf8");
          xml += decipher.final("utf8");
          res = await this.parseXML(xml);
        } catch (error) {
          res = { err: "解码出错", raw: req };
        }
      } else {
        res = this.validate(res);
      }
    }
    return new REQUEST(res);
  }
  async front(order: string): Promise<REQUEST> {
    return await this.query(order);
  }
}
export default PAY;